package chapter16;

import java.util.Random;

/**
 * @author lzy
 * @version v1.0
 * Create DateTime: 2019/2/23 16:01
 */
public class AccountMgr {
    public static void main(String[] args) {
        final int accountNum = 10;
        final Account[] accounts = new Account[accountNum];
        final Random random = new Random();
        for (int i = 0; i < accountNum; i++) {
            accounts[i] = new Account(random.nextInt(10000));
        }
        int threadNum = 100;
        Thread[] threads = new Thread[threadNum];
        for (int i = 0; i < threadNum; i++) {
            threads[i] = new Thread(() -> {
                int loopNum = 100;
                for (int j = 0; j < loopNum; j++) {
                    int i1 = random.nextInt(accountNum);
                    int j1 = random.nextInt(accountNum);
                    int money = random.nextInt(10);
                    if (i1 != j1) {
                        try {
                            transferModified(accounts[i1], accounts[j1], money);
                        } catch (Exception e) {

                        }
                    }
                }
            });
            threads[i].start();
        }
    }

    public static void transferModified(Account from, Account to, double money) throws NoEnoughMoneyException {
        boolean success = false;
        do {
            success = tryTransfer(from, to, money);
            if (!success) {
                Thread.yield();
            }
            System.out.println("transfer " + money + " from " + from + " to " + to);
        } while (!success);
    }

    public static boolean tryTransfer(Account from, Account to, double money) throws NoEnoughMoneyException {
        if (from.tryLock()) {
            try {
                if (to.tryLock()) {
                    try {
                        if (from.getMoney() > money) {
                            from.reduce(money);
                            to.add(money);
                        } else {
                            throw new NoEnoughMoneyException();
                        }
                        return true;
                    } finally {
                        to.unlock();
                    }
                }
            } finally {
                from.unlock();
            }
        }
        return false;
    }

    /**
     * 这种写法是错误的，因为如果两个账户同时给对方转账，可能会造成死锁
     *
     * @param from
     * @param to
     * @param money
     * @throws NoEnoughMoneyException
     */
    public static void transfer(Account from, Account to, double money) throws NoEnoughMoneyException {
        from.lock();
        try {
            to.lock();
            try {
                if (from.getMoney() > money) {
                    from.reduce(money);
                    to.add(money);
                } else {
                    throw new NoEnoughMoneyException();
                }
            } finally {
                to.unlock();
            }
        } finally {
            from.unlock();
        }
    }

    public static class NoEnoughMoneyException extends Exception {
    }
}
